Example 2 Clustering Geometric ObjectsΒΆ

In this example we will look at a few of the tools provided by the clifford package for (4,1) conformal geometric algebra (CGA) and see how we can use them in a practical setting to cluster geometric objects via the simple K-means clustering algorithm provided in clifford.tools

As before the first step in using the package for CGA is to generate and import the algebra:

In [1]:
from clifford.g3c import *
print('e1*e1 ', e1*e1)
print('e2*e2 ', e2*e2)
print('e3*e3 ', e3*e3)
print('e4*e4 ', e4*e4)
print('e5*e5 ', e5*e5)
e1*e1  1.0
e2*e2  1.0
e3*e3  1.0
e4*e4  1.0
e5*e5  -1.0

The tools submodule of the clifford package contains a wide array of algorithms and tools that can be useful for manipulating objects in CGA. In this case we will be generating a large number of objects and then segmenting them into clusters.

We first need an algorithm for generating a cluster of objects in space. We will construct this cluster by generating a random object and then repeatedly disturbing this object by some small fixed amount and storing the result:

In [2]:
from clifford.tools.g3c import *
import numpy as np

def generate_random_object_cluster(n_objects, object_generator, max_cluster_trans=1.0, max_cluster_rot=np.pi/8):
    """ Creates a cluster of random objects """
    ref_obj = object_generator()
    cluster_objects = []
    for i in range(n_objects):
        r = random_rotation_translation_rotor(maximum_translation=max_cluster_trans, maximum_angle=max_cluster_rot)
        new_obj = apply_rotor(ref_obj, r)
        cluster_objects.append(new_obj)
    return cluster_objects

We can use this function to create a cluster and then we can visualise this cluster with GAOnline using the built in tools in clifford.

In [3]:
from clifford.tools.g3c.GAOnline import *
clustered_circles = generate_random_object_cluster(10, random_circle)
sc = GAScene()
for c in clustered_circles:
    sc.add_circle(c,'rgb(255,0,0)')
print(sc)
DrawCircle(-(0.36997^e123) + (2.26921^e124) + (2.29868^e125) - (1.95134^e134) - (2.00433^e135) + (0.16961^e145) + (3.3972^e234) + (3.49326^e235) - (0.31865^e245) + (0.0201^e345),rgb(255,0,0));
DrawCircle(-(0.45784^e123) + (3.21337^e124) + (3.27226^e125) - (1.02131^e134) - (1.04321^e135) + (0.0224^e145) + (3.95276^e234) + (4.04741^e235) - (0.15592^e245) + (0.022^e345),rgb(255,0,0));
DrawCircle(-(0.38956^e123) + (2.73692^e124) + (2.78475^e125) - (1.88658^e134) - (1.93005^e135) + (0.07372^e145) + (3.48669^e234) + (3.58011^e235) - (0.2282^e245) + (0.06338^e345),rgb(255,0,0));
DrawCircle(-(0.36938^e123) + (1.9374^e124) + (1.9762^e125) - (2.45153^e134) - (2.50124^e135) + (0.00318^e145) + (3.71576^e234) + (3.81026^e235) - (0.10524^e245) + (0.12708^e345),rgb(255,0,0));
DrawCircle(-(0.44186^e123) + (2.91469^e124) + (2.96783^e125) - (0.47849^e134) - (0.46135^e135) - (0.17056^e145) + (3.82889^e234) + (3.92782^e235) - (0.19212^e245) + (0.2556^e345),rgb(255,0,0));
DrawCircle(-(0.45531^e123) + (2.54915^e124) + (2.59205^e125) - (0.95253^e134) - (0.96369^e135) - (0.02727^e145) + (4.35127^e234) + (4.45587^e235) - (0.17562^e245) + (0.11218^e345),rgb(255,0,0));
DrawCircle(-(0.3973^e123) + (2.73708^e124) + (2.78067^e125) - (1.22314^e134) - (1.24341^e135) + (0.00542^e145) + (3.59296^e234) + (3.6959^e235) - (0.315^e245) + (0.13366^e345),rgb(255,0,0));
DrawCircle(-(0.57899^e123) + (4.41944^e124) + (4.48151^e125) - (1.04794^e134) - (1.06337^e135) + (0.00548^e145) + (3.13881^e234) + (3.2327^e235) - (0.38013^e245) + (0.08624^e345),rgb(255,0,0));
DrawCircle(-(0.40334^e123) + (1.87269^e124) + (1.90855^e125) - (0.96659^e134) - (0.99688^e135) + (0.05467^e145) + (4.5403^e234) + (4.64376^e235) - (0.07664^e245) - (0.09299^e345),rgb(255,0,0));
DrawCircle(-(0.38815^e123) + (2.19481^e124) + (2.23741^e125) - (1.6768^e134) - (1.70112^e135) - (0.04649^e145) + (3.93778^e234) + (4.04025^e235) - (0.14725^e245) + (0.1959^e345),rgb(255,0,0));

This cluster generation function appears in clifford tools by default and it can be imported as follows:

In [4]:
from clifford.tools.g3c import generate_random_object_cluster

Now that we can generate individual clusters we would like to generate many:

In [5]:
def generate_n_clusters( object_generator, n_clusters, n_objects_per_cluster ):
    object_clusters = []
    for i in range(n_clusters):
        cluster_objects = generate_random_object_cluster(n_objects_per_cluster, object_generator,
                                                         max_cluster_trans=0.5, max_cluster_rot=np.pi / 16)
        object_clusters.append(cluster_objects)
    all_objects = [item for sublist in object_clusters for item in sublist]
    return all_objects, object_clusters

Again this function appears by default in clifford tools and we can easily visualise the result:

In [6]:
from clifford.tools.g3c import generate_n_clusters

all_objects, object_clusters = generate_n_clusters(random_circle, 2, 5)
sc = GAScene()
for c in all_objects:
    sc.add_circle(c,'rgb(255,0,0)')
print(sc)
DrawCircle(-(0.17174^e123) + (5.35479^e124) + (5.38886^e125) + (0.829^e134) + (0.8233^e135) + (0.34221^e145) + (6.06454^e234) + (6.09535^e235) + (0.24209^e245) - (0.35009^e345),rgb(255,0,0));
DrawCircle(-(0.23353^e123) + (5.0236^e124) + (5.05694^e125) + (1.20338^e134) + (1.19561^e135) + (0.33894^e145) + (6.54857^e234) + (6.57972^e235) + (0.26469^e245) - (0.37842^e345),rgb(255,0,0));
DrawCircle(-(0.21034^e123) + (3.44402^e124) + (3.47206^e125) + (2.2989^e134) + (2.29897^e135) + (0.30527^e145) + (6.81565^e234) + (6.85248^e235) + (0.30555^e245) - (0.40016^e345),rgb(255,0,0));
DrawCircle(-(0.22943^e123) + (4.7089^e124) + (4.74043^e125) + (1.74275^e134) + (1.73872^e135) + (0.32209^e145) + (6.84199^e234) + (6.87563^e235) + (0.24994^e245) - (0.3755^e345),rgb(255,0,0));
DrawCircle(-(0.2422^e123) + (5.25449^e124) + (5.28695^e125) + (1.4847^e134) + (1.47929^e135) + (0.31633^e145) + (6.96678^e234) + (6.99932^e235) + (0.22761^e245) - (0.3551^e345),rgb(255,0,0));
DrawCircle((0.03253^e123) + (0.66619^e124) + (0.67017^e125) + (3.70677^e134) + (3.6911^e135) - (0.77409^e145) - (0.02825^e234) - (0.02195^e235) + (0.13237^e245) + (0.70371^e345),rgb(255,0,0));
DrawCircle((0.02484^e123) + (0.73633^e124) + (0.73834^e125) + (2.53694^e134) + (2.52154^e135) - (0.66179^e145) - (0.03757^e234) - (0.02986^e235) + (0.23169^e245) + (0.7645^e345),rgb(255,0,0));
DrawCircle((0.03793^e123) + (1.26428^e124) + (1.26608^e125) + (2.85825^e134) + (2.84305^e135) - (0.64188^e145) + (0.39905^e234) + (0.40722^e235) + (0.25324^e245) + (0.77512^e345),rgb(255,0,0));
DrawCircle((0.02765^e123) + (0.62199^e124) + (0.62427^e125) + (4.18394^e134) + (4.16847^e135) - (0.69327^e145) - (0.63685^e234) - (0.62934^e235) + (0.22135^e245) + (0.77911^e345),rgb(255,0,0));
DrawCircle((0.02895^e123) + (1.01153^e124) + (1.01259^e125) + (3.49854^e134) + (3.48316^e135) - (0.6654^e145) - (0.36922^e234) - (0.36127^e235) + (0.29123^e245) + (0.76439^e345),rgb(255,0,0));

Given that we can now generate multiple clusters of objects we can test algorithms for segmenting them.

The function run_n_clusters below generates a lot of objects distributed into n clusters and then attempts to segment the objects to recover the clusters.

In [7]:
from clifford.tools.g3c.object_clustering import n_clusters_objects
import time

def run_n_clusters( object_generator, n_clusters, n_objects_per_cluster, n_shotgunning):
    all_objects, object_clusters = generate_n_clusters( object_generator, n_clusters, n_objects_per_cluster )
    [new_labels, centroids, start_labels, start_centroids] = n_clusters_objects(n_clusters, all_objects,
                                                                                initial_centroids=None,
                                                                                n_shotgunning=n_shotgunning,
                                                                                averaging_method='unweighted')
    return all_objects, new_labels, centroids

Lets try it!

In [8]:
from clifford.tools.g3c.object_clustering import visualise_n_clusters

object_generator = random_circle

n_clusters = 3
n_objects_per_cluster = 10
n_shotgunning = 60
all_objects, labels, centroids = run_n_clusters(object_generator, n_clusters,
                                                     n_objects_per_cluster, n_shotgunning)

sc = visualise_n_clusters(all_objects, centroids, labels, object_type='circle',
                     color_1=np.array([255, 0, 0]), color_2=np.array([0, 255, 0]))
print(sc)
DrawCircle((0.05357^e123) + (3.17031^e124) + (3.19478^e125) + (2.12331^e134) + (2.14473^e135) + (0.29783^e145) + (5.23562^e234) + (5.28164^e235) + (0.33262^e245) - (0.26908^e345),rgb(127, 127, 0));
DrawCircle((0.03786^e123) + (2.4296^e124) + (2.44719^e125) + (1.27047^e134) + (1.28221^e135) + (0.16286^e145) + (6.56419^e234) + (6.61642^e235) + (0.30193^e245) - (0.28213^e345),rgb(127, 127, 0));
DrawCircle((0.02546^e123) + (2.92207^e124) + (2.94402^e125) + (1.47368^e134) + (1.48639^e135) + (0.1883^e145) + (6.34588^e234) + (6.3962^e235) + (0.3038^e245) - (0.25571^e345),rgb(127, 127, 0));
DrawCircle((0.06304^e123) + (3.26949^e124) + (3.29199^e125) + (2.30165^e134) + (2.32162^e135) + (0.21421^e145) + (6.30428^e234) + (6.35192^e235) + (0.22107^e245) - (0.25741^e345),rgb(127, 127, 0));
DrawCircle((0.03481^e123) + (2.66661^e124) + (2.68721^e125) + (1.71896^e134) + (1.7352^e135) + (0.22684^e145) + (5.94359^e234) + (5.99346^e235) + (0.30302^e245) - (0.31027^e345),rgb(127, 127, 0));
DrawCircle((0.08697^e123) + (3.60641^e124) + (3.6309^e125) + (1.35678^e134) + (1.3711^e135) + (0.21164^e145) + (6.06245^e234) + (6.11113^e235) + (0.31115^e245) - (0.23871^e345),rgb(127, 127, 0));
DrawCircle((0.07437^e123) + (3.50342^e124) + (3.52709^e125) + (1.72466^e134) + (1.74055^e135) + (0.19976^e145) + (6.33344^e234) + (6.38204^e235) + (0.27366^e245) - (0.2264^e345),rgb(127, 127, 0));
DrawCircle((0.04683^e123) + (3.30652^e124) + (3.33036^e125) + (1.54998^e134) + (1.56404^e135) + (0.20364^e145) + (6.29136^e234) + (6.34043^e235) + (0.26216^e245) - (0.26458^e345),rgb(127, 127, 0));
DrawCircle((0.04227^e123) + (2.74767^e124) + (2.76737^e125) + (1.44962^e134) + (1.46271^e135) + (0.175^e145) + (6.42173^e234) + (6.47287^e235) + (0.33203^e245) - (0.23384^e345),rgb(127, 127, 0));
DrawCircle((0.06609^e123) + (2.69339^e124) + (2.71219^e125) + (0.8806^e134) + (0.89094^e135) + (0.17068^e145) + (6.3984^e234) + (6.45051^e235) + (0.30334^e245) - (0.3063^e345),rgb(127, 127, 0));
DrawCircle((0.03935^e123) - (0.67306^e124) - (0.68398^e125) - (2.46439^e134) - (2.49612^e135) - (0.14117^e145) + (4.98958^e234) + (5.05822^e235) + (0.21093^e245) - (0.27423^e345),rgb(255, 0, 0));
DrawCircle((0.0319^e123) - (0.52531^e124) - (0.53341^e125) - (2.28829^e134) - (2.31704^e135) - (0.10708^e145) + (5.33973^e234) + (5.41006^e235) + (0.19656^e245) - (0.23227^e345),rgb(255, 0, 0));
DrawCircle((0.11105^e123) - (1.22862^e124) - (1.24783^e125) - (2.46082^e134) - (2.48756^e135) - (0.12983^e145) + (5.17932^e234) + (5.24827^e235) + (0.13298^e245) - (0.28097^e345),rgb(255, 0, 0));
DrawCircle((0.12148^e123) - (0.64734^e124) - (0.65962^e125) - (2.57369^e134) - (2.60179^e135) - (0.11046^e145) + (5.33535^e234) + (5.40533^e235) + (0.16651^e245) - (0.24839^e345),rgb(255, 0, 0));
DrawCircle((0.08622^e123) - (0.95849^e124) - (0.97533^e125) - (1.95895^e134) - (1.98091^e135) - (0.13862^e145) + (4.96467^e234) + (5.03589^e235) + (0.1782^e245) - (0.35378^e345),rgb(255, 0, 0));
DrawCircle((0.0542^e123) - (0.66149^e124) - (0.67094^e125) - (2.6986^e134) - (2.73067^e135) - (0.07944^e145) + (5.3835^e234) + (5.4522^e235) + (0.10082^e245) - (0.23519^e345),rgb(255, 0, 0));
DrawCircle((0.10252^e123) - (0.97879^e124) - (0.99457^e125) - (2.07802^e134) - (2.1004^e135) - (0.10628^e145) + (5.46673^e234) + (5.53806^e235) + (0.16065^e245) - (0.25255^e345),rgb(255, 0, 0));
DrawCircle((0.11605^e123) - (0.21691^e124) - (0.22398^e125) - (2.65748^e134) - (2.68722^e135) - (0.10625^e145) + (5.14249^e234) + (5.21252^e235) + (0.18227^e245) - (0.28599^e345),rgb(255, 0, 0));
DrawCircle((0.03935^e123) - (0.6107^e124) - (0.61962^e125) - (2.63831^e134) - (2.67064^e135) - (0.09638^e145) + (5.31593^e234) + (5.38459^e235) + (0.13964^e245) - (0.23566^e345),rgb(255, 0, 0));
DrawCircle((0.10636^e123) - (0.07528^e124) - (0.07967^e125) - (2.87347^e134) - (2.90636^e135) - (0.09536^e145) + (5.2719^e234) + (5.34072^e235) + (0.16895^e245) - (0.2291^e345),rgb(255, 0, 0));
DrawCircle((0.54416^e123) + (3.35443^e124) + (3.44411^e125) - (0.81602^e134) - (0.78719^e135) + (0.31216^e145) - (4.13712^e234) - (4.18557^e235) + (0.38319^e245) + (0.29178^e345),rgb(0, 255, 0));
DrawCircle((0.42009^e123) + (2.12509^e124) + (2.21208^e125) - (0.99494^e134) - (0.9664^e135) + (0.35044^e145) - (3.54843^e234) - (3.60169^e235) + (0.46538^e245) + (0.36728^e345),rgb(0, 255, 0));
DrawCircle((0.49211^e123) + (3.55959^e124) + (3.65448^e125) - (0.73723^e134) - (0.71742^e135) + (0.28541^e145) - (3.61437^e234) - (3.65708^e235) + (0.38801^e245) + (0.20944^e345),rgb(0, 255, 0));
DrawCircle((0.46846^e123) + (3.15127^e124) + (3.2411^e125) - (0.8405^e134) - (0.8185^e135) + (0.30915^e145) - (3.69726^e234) - (3.74892^e235) + (0.36143^e245) + (0.26631^e345),rgb(0, 255, 0));
DrawCircle((0.39175^e123) + (2.56681^e124) + (2.65549^e125) - (0.67106^e134) - (0.64408^e135) + (0.32871^e145) - (3.32537^e234) - (3.37664^e235) + (0.4169^e245) + (0.31686^e345),rgb(0, 255, 0));
DrawCircle((0.55388^e123) + (3.35436^e124) + (3.44504^e125) - (1.94575^e134) - (1.93548^e135) + (0.38074^e145) - (3.88473^e234) - (3.93851^e235) + (0.31024^e245) + (0.26098^e345),rgb(0, 255, 0));
DrawCircle((0.52861^e123) + (3.47383^e124) + (3.56242^e125) - (1.17377^e134) - (1.15181^e135) + (0.34103^e145) - (3.89684^e234) - (3.9506^e235) + (0.29977^e245) + (0.28127^e345),rgb(0, 255, 0));
DrawCircle((0.55679^e123) + (3.72892^e124) + (3.81483^e125) - (0.735^e134) - (0.70139^e135) + (0.33845^e145) - (4.03613^e234) - (4.0882^e235) + (0.27399^e245) + (0.31232^e345),rgb(0, 255, 0));
DrawCircle((0.48645^e123) + (2.89474^e124) + (2.97986^e125) - (0.93293^e134) - (0.89936^e135) + (0.36304^e145) - (3.81527^e234) - (3.86864^e235) + (0.35005^e245) + (0.36568^e345),rgb(0, 255, 0));
DrawCircle((0.50709^e123) + (3.18826^e124) + (3.27683^e125) - (0.90889^e134) - (0.88272^e135) + (0.32327^e145) - (3.92686^e234) - (3.97874^e235) + (0.35969^e245) + (0.29563^e345),rgb(0, 255, 0));
DrawCircle((0.4956^e123) + (3.13527^e124) + (3.22458^e125) - (0.97467^e134) - (0.94931^e135) + (0.33604^e145) - (3.81662^e234) - (3.86782^e235) + (0.36386^e245) + (0.29595^e345),rgb(0,0,0));
DrawCircle((0.05334^e123) + (3.04429^e124) + (3.06613^e125) + (1.59219^e134) + (1.60722^e135) + (0.20583^e145) + (6.21402^e234) + (6.26379^e235) + (0.29566^e245) - (0.26551^e345),rgb(0,0,0));
DrawCircle((0.08128^e123) - (0.66177^e124) - (0.67311^e125) - (2.47448^e134) - (2.50329^e135) - (0.11068^e145) + (5.2559^e234) + (5.32575^e235) + (0.16458^e245) - (0.26368^e345),rgb(0,0,0));